package core.reports;

import core.*;
import java.text.DecimalFormat;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * This class will create the Product Statistics Report:
 * It will be responsible for getting the necessary data
 * through the visitor pattern, and then constructing the report
 * through the builder pattern.
 * 
 */
public class ProductStatisticsGuide implements IDirectorVisitor 
{
    int n = 0;
    ItemManager itemManager;
    ProductManager productManager;
    CoreFacade core;
    private List<ProductStatisticsNode> productStatistics;
    
    private String description;
    private String barcode;
    private String size;
    private String threeMonthSupply;
    
    private String supplyCur;
    private String supplyAve;
    
    private String supplyMin;
    private String supplyMax;
    
    private String supplyUsed;
    private String supplyAdded;
    
    private String shelfLife;
    
    private String usedAvg;
    private String usedMax;
    
    private String curAvg;
    private String curMax;
    
        
    public ProductStatisticsGuide(int n)
    {
        this.n = n;
        core = CoreFacade.getInstance();
        productManager = core.getPM();
        itemManager = core.getIM();
        
        productStatistics = new ArrayList<ProductStatisticsNode>();
    }
    
	@Override
	/**
	 * When creating the Product statistics report this method
	 * will get the necessary information from a productgroup
	 * @param pg the productGroup
	 * @return true if it was successful
	 */
	public boolean visitProductGroup(ProductGroup pg, TraversalOrder order) 
	{
            //method should not be used
		return false;
	}

	@Override
	/**
	 * When creating the Product statistics report this method
	 * will get the necessary information from a storage unit
	 * @param su the storage unit
	 * @return true if it was successful
	 */
	public boolean visitStorageUnit(StorageUnit su, TraversalOrder order) 
	{
            //method should not be used
		return false;
	}

	
	@Override
	/**
	 * After getting the data from the visit methods, this will
	 * then instruct the appropriate builder how to create the 
	 * necessary report
	 * @return true if it was successful
	 */
	public String construct(String nameOfBuilder) 
	{
            build();
            
            Collections.sort(productStatistics,new Comparator<ProductStatisticsNode>() {
                @Override
            public int compare(ProductStatisticsNode psn1, ProductStatisticsNode psn2) 
                {
                    return psn1.getDescription().compareTo(psn2.getDescription());
                }
            });
            
            String filePath = "";
            IBuilder builder;
            
            if(nameOfBuilder.equals(ReportConstants.HTML_BUILDER))
            {
                builder = new HTMLBuilder("reports/ProductStatistics-"
                        + new Date().getTime() + ".html");
                filePath = ((HTMLBuilder)builder).getFileName();
            }
            else
            {
                builder = new PDFBuilder("reports/ProductStatistics-"
                        + new Date().getTime() + ".pdf");
                filePath = ((PDFBuilder)builder).getFileName();
            } 
            
            builder.addTitle("Product Report(" + n + " Months)");
            
            builder.addBlankLine();
            
            builder.addTable(10);
            builder.addColumnHeader("Description");
            builder.addColumnHeader("Barcode");
            builder.addColumnHeader("Size");
            builder.addColumnHeader("3-Month Supply");
            builder.addColumnHeader("Supply: Cur/Avg");
            builder.addColumnHeader("Supply: Min/Max");
            builder.addColumnHeader("Supply: Used/Added");
            builder.addColumnHeader("Shelf Life");
            builder.addColumnHeader("Used Age: Avg/Max");
            builder.addColumnHeader("Cur Age: Avg/Max");
            
            
            for(int i = 0; i < productStatistics.size(); i++)
            {
                builder.addCell(productStatistics.get(i).getDescription());
                builder.addCell(productStatistics.get(i).getBarcode());
                builder.addCell(productStatistics.get(i).getSize());
                builder.addCell(productStatistics.get(i).getThreeMonthSupply());
                builder.addCell(productStatistics.get(i).getSupplyCur()
                        + "/" + productStatistics.get(i).getSupplyAve());
                builder.addCell(productStatistics.get(i).getSupplyMin()
                        + "/" + productStatistics.get(i).getSupplyMax());
                builder.addCell(productStatistics.get(i).getSupplyUsed()
                        + "/" + productStatistics.get(i).getSupplyAdded());
                
                builder.addCell(productStatistics.get(i).getShelfLife());
                
                builder.addCell(productStatistics.get(i).getUsedAvg()
                        + "/" + productStatistics.get(i).getUsedMax());
                
                builder.addCell(productStatistics.get(i).getCurAvg()
                        + "/" + productStatistics.get(i).getCurMax());
            }
            
            builder.closeCurrentTable();
            builder.addBlankLine();
            
            builder.closeCurrentTable();
            builder.finish();
                
            
            
            return filePath;
	}
        
    private void build()
    {
        List<Product> allProducts = productManager.getAllProducts();
        
        for(Product product : allProducts)
        {
            //easy
            description = product.getDescription();
            barcode = product.getBarcodeString();
            size = product.getAmount().toString();
            threeMonthSupply = product.getThreeMonthSupply()+"";
            shelfLife = product.getShelfLife()+"";
            
            
            getSupplyCur(product);
            getSupplyAve(product);
            
            getSupplyAdded(product);
            getSupplyUsed(product);
            
            
            getUsedAvgMax(product);
            getCurrentAvgandMax(product);
            /*
            supplyCur;
            supplyAve;
                /*
                supplyCur;
                supplyAve; //Eric


                supplyMin; spencer h
                supplyMax;

                supplyUsed; //Eric
                supplyAdded;

                usedAvg; //Aaron
                usedMax;

                curAvg; //Steel
                curMax;
                */ 
                calcMinMaxSupply(product);
                ProductStatisticsNode psn = 
                        new ProductStatisticsNode(description, barcode, size, 
                        threeMonthSupply, supplyCur, supplyAve,
                        supplyMin, supplyMax, supplyUsed, supplyAdded, shelfLife,
                        usedAvg, usedMax, curAvg, curMax);
                
                productStatistics.add(psn);
                clearGlobals();
            }
            
            
            
        }
    private void getUsedAvgMax(Product product) 
    {
        Set<Item> removedItems = new HashSet<Item>(itemManager.getRemovedItems());
        
        Iterator<Item> iter = removedItems.iterator();
        Item cur;
        float totalDays = 0;
        float count = 0;
        usedMax = "0";
        while(iter.hasNext())
        {
        	cur = iter.next();
        	if(cur.getProduct().equals(product))
        	{
        		try
				{
        			int days = daysBetween(cur.getEntryDate(), cur.getExitTime());
        			if(Integer.parseInt(usedMax) < days)
        				usedMax = "" + days;
        			totalDays += days;
					count++;
				} catch (Exception e)
				{
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
        	}
        }
        
        if(count >0)
        {
        	usedAvg = "" + String.format("%.1f", totalDays/count) + " days ";
        }
        else
        	usedAvg = " 0.0 days";
        usedMax = " " + usedMax + " days";
    }

        private void calcMinMaxSupply(Product p)
        {
        	Calendar end = new GregorianCalendar();
        	Calendar start = (Calendar)end.clone();
        	start.add(Calendar.MONTH, -this.n);
        	
        	        	
        	if (start.before(p.getCreationDate()))
        	{
        		start = (Calendar) p.getCreationDate().clone();
        	}
        	
        	int minSupply;
        	int maxSupply;
        	int currentTotal;
        	
        	Set<Item> curItems = itemManager.getItems(p);
        	Collection<Item> remItems = itemManager.getRemovedItems();
        	
        	Set<Item> itemsAtStart = new HashSet<Item>();
        	
        	Iterator it = curItems.iterator();
        	while (it.hasNext())
        	{
        		Item tmpItem = (Item) it.next();
        		
        		// This needs to be fixed, not grabbing items adding on same day as product
        		if ((tmpItem.getEntryDate().before(start)) )
        		{
        			itemsAtStart.add(tmpItem);
        		}
        	}
        	
        	it = remItems.iterator();
        	while (it.hasNext())
        	{
        		Item tmpItem = (Item) it.next();
        		
        		if (!tmpItem.getProduct().equals(p)) continue;
        		
        		if ((tmpItem.getEntryDate().before(start)) )
				{
        			Calendar itemExitTime = tmpItem.getExitTime();
        			if ((itemExitTime != null) && (itemExitTime.after(start)))
        			{
        				itemsAtStart.add(tmpItem);
        			}
				}
        	}
        	
        	//set the current counts
        	currentTotal = maxSupply = minSupply = itemsAtStart.size();
        	/*currentTotal = 0;
        	maxSupply = 0;
        	minSupply = itemsAtStart.size();*/
        	
        	int looped = 0;
        	while(start.before(end))
        	{
        		//System.out.println("Calc stats " + looped);
        		
        		currentTotal += numAddedOn(p, start);
        		currentTotal -= numRemovedOn(p, start);
        		
        		if (currentTotal > maxSupply) maxSupply = currentTotal;
        		if (currentTotal < minSupply) minSupply = currentTotal;
        	
        		//Incrementing loop
        		start.add(Calendar.DAY_OF_MONTH, 1);
        		looped++;
        	}
        	
        	supplyMin = Integer.toString(minSupply);
        	supplyMax = Integer.toString(maxSupply);
        }
        
        private int numAddedOn(Product p, Calendar date)
        {
        	int numAdded = 0;
        	
        	Set<Item> curItems = itemManager.getItems(p);
        	Collection<Item> remItems = itemManager.getRemovedItems();
        	
        	Iterator it = curItems.iterator();
        	
        	while(it.hasNext())
        	{
        		Item tmpItem = (Item) it.next();
        		
        		if (checkIfDatesEqual(tmpItem.getEntryDate(), date))
        		{
        			numAdded++;
        		}
        	}
        	
        	it = remItems.iterator();
        	
        	while(it.hasNext())
        	{
        		Item tmpItem = (Item) it.next();
        		
        		if ((tmpItem.getProduct().equals(p)) && 
        				(checkIfDatesEqual(tmpItem.getEntryDate(), date)))
        		{
        			numAdded++;
        		}
        	}
        	
        	return numAdded;
        }
        
        private int numRemovedOn(Product p, Calendar date)
        {
        	int numRemoved = 0;
        	
        	Collection<Item> remItems = itemManager.getRemovedItems();
        	
        	Iterator it = remItems.iterator();
        	
        	while(it.hasNext())
        	{
        		Item tmpItem = (Item) it.next();
        		
        		if ((tmpItem.getProduct().equals(p)) && 
        				(checkIfDatesEqual(tmpItem.getExitTime(), date)))
        		{
        			numRemoved++;
        		}
        	}
        	
        	return numRemoved;
        }
        
    private boolean checkIfDatesEqual(Calendar date1, Calendar date2)
    {
            return (date1.get(Calendar.YEAR) == date2.get(Calendar.YEAR)) && 
                            (date1.get(Calendar.MONTH) == date2.get(Calendar.MONTH)) && 
                            (date1.get(Calendar.DAY_OF_MONTH) == date2.get(Calendar.DAY_OF_MONTH));
    }
        
    private void getSupplyCur(Product product) 
    {
        Set<Item> itemsOfProduct = itemManager.getItems(product);
        
        supplyCur = itemsOfProduct.size()+"";
    }

    private void getSupplyAve(Product product) 
    {
       Set<Item> itemsOfProduct = itemManager.getItems(product);
       Collection<Item> removed = itemManager.getRemovedItems();
       List<Integer> dateDifference1 = new ArrayList();
       List<Integer> dateDifference2 = new ArrayList();
       
       Calendar now = new GregorianCalendar();
       Calendar before = new GregorianCalendar();
       before.add(Calendar.MONTH, -n);
       if(product.getCreationDate().after(before))
    	   before.setTime(product.getCreationDate().getTime());
       
       int nDaysAgo = daysBetween(before, now);
       
       for(Item i : itemsOfProduct)
       {   
           int diff = daysBetween(i.getEntryDate(), now);
           if(nDaysAgo >= diff) //filters out things added before n
           {
                dateDifference1.add(diff);
           }
       }
       for(Item i : removed)
       {
           if(!i.getProduct().equals(product)){continue;}
           int diff = daysBetween(i.getEntryDate(), now);
           if(nDaysAgo > diff)
           {
               dateDifference2.add(diff);
           }
       }
       
       int dailyAmount = 0;
       double sum = 0;
       
       for(int i = nDaysAgo; i >= 0; i--)
       {
           if(dateDifference1.contains(i))
           {
               for(Integer in : dateDifference1)
               {
                   if(in == i)
                       dailyAmount++;
               }
           }
           if(dateDifference2.contains(i))
           {
               for(Integer in : dateDifference2)
               {
                   if(in == i)
                       dailyAmount--;
               }
           }
           sum += dailyAmount;
           dailyAmount=0;
       }
       
       sum = sum / nDaysAgo; //average
       DecimalFormat oneDigit = new DecimalFormat("#,##0.0");//format to 1 
       supplyAve = oneDigit.format(sum);
    }
    
    /**
     * Current Age (Average): For all Items of this 
     * Product currently in storage,the average number of
     * days spent in storage.
     * Current Age (Maximum): For all Items of this
     * Product currently in storage, the maximum number
     * of days spent in storage.
     */    
    private void getCurrentAvgandMax(Product product)
    {
    	List<Integer> daysInStorage = new ArrayList<Integer>();
    	Set<Item> items = itemManager.getItems(product);
    	int max = 0;
    	for(Item i : items)
    	{
    		// get the item, and find number of days from entry date to today
    		Calendar itemEntryDate = i.getEntryDate();
    		Calendar now = new GregorianCalendar();
    		int daysInSystem = daysBetween(itemEntryDate, now);
    		if(daysInSystem > max)
    			max = daysInSystem;
    		daysInStorage.add(daysInSystem);
    	}
    	int sum = 0;
    	for(int i = 0; i < daysInStorage.size(); i++)
    	{
    		sum = sum + daysInStorage.get(i);
    	}
    	
    	double average = (double)sum/(double)daysInStorage.size();
    	curAvg = String.format("%.1f", average);
    	curMax = Integer.toString(max);
    }
    
    
    private void getSupplyAdded(Product product) 
    {
       Set<Item> itemsOfProduct = itemManager.getItems(product);
       List<Integer> dateDifference = new ArrayList();
       
       Calendar now = new GregorianCalendar();
       Calendar before = new GregorianCalendar();
       before.add(Calendar.MONTH, -n);
       
       int nDaysAgo = daysBetween(before, now);
       
       for(Item i : itemsOfProduct)
       {   
           int diff = daysBetween(i.getEntryDate(), now);
           if(nDaysAgo > diff) //filters out things added before n
           {
                dateDifference.add(diff);
           }
       }
       
       int added = 0;
       
       for(int i = nDaysAgo; i >= 0; i--)
       {
           if(dateDifference.contains(i))
           {
               for(Integer in : dateDifference)
               {
                   if(in == i)
                       added++;
               }
           }
            
       }
       
       supplyAdded = added + "";
       
    }
    
    private void getSupplyUsed(Product product) 
    {
       Collection<Item> removed = itemManager.getRemovedItems();
       
       List<Integer> dateDifference = new ArrayList();
       
       Calendar now = new GregorianCalendar();
       Calendar before = new GregorianCalendar();
       before.add(Calendar.MONTH, -n);
       
       int nDaysAgo = daysBetween(before, now);
       
       for(Item i : removed)
       {   
            try {
                if(!i.getProduct().equals(product)){continue;}
                int diff = daysBetween(i.getExitTime(), now);
                if(nDaysAgo > diff) //filters out things added before n
                {
                     dateDifference.add(diff);
                }
            } catch (Exception ex) {
                Logger.getLogger(ProductStatisticsGuide.class.getName()).log(
                		Level.SEVERE, null, ex);
            }
       }
       
       int used = 0;
       
       for(int i = nDaysAgo; i >= 0; i--)
       {
           if(dateDifference.contains(i))
           {
               for(Integer in : dateDifference)
               {
                   if(in == i)
                       used++;
               }
           }
       }
       
       supplyUsed = used + "";
       
    }
    
    
    private static int daysBetween(Calendar startDate, Calendar endDate) {  
        Calendar date = (Calendar) startDate.clone();  
        int daysBetween = 0;  
            while (date.before(endDate)) {  
                date.add(Calendar.DAY_OF_MONTH, 1);  
                daysBetween++;  
            }  
        return daysBetween;  
    } 

    private void clearGlobals() 
    {
     description = "";
     barcode = "";
     size = "";
     threeMonthSupply = "";
    
     supplyCur = "";
     supplyAve = "";
    
     supplyMin = "";
     supplyMax = "";
    
     supplyUsed = "";
     supplyAdded = "";
    
     shelfLife = "";
    
     usedAvg = "";
     usedMax = "";
    
     curAvg = "";
     curMax = "";
       
    }
    
    /**
     * builds the product statistics for testing purposes
     * @return a list of the product statistics
     */
    public List<ProductStatisticsNode> getProductStatistics()
    {
        build();
        return productStatistics;
    }
        
        

    
    public double getUsedAverage(String barcode)
    {
    	double returnVal = 0.0;
    	
    	for (int i = 0; i < productStatistics.size(); i++)
    	{
    		ProductStatisticsNode pn =productStatistics.get(i); 
    		
    		if (pn.getBarcode().equals(barcode))
    		{
    			System.out.println(pn.getUsedAvg());
    			returnVal = Double.parseDouble(pn.getUsedAvg().
                                substring(0, pn.getUsedAvg().indexOf(" days")));
    			break;
    		}
    	}
    	
    	return returnVal;
    }
    
    public double getAvgAge(String barcode)
    {
    	double returnVal = 0.0;
    	
    	for (int i=0; i< productStatistics.size(); i++)
    	{
    		ProductStatisticsNode pn = productStatistics.get(i);
    		
    		if (pn.getBarcode().equals(barcode))
    		{
    			System.out.println(pn.getCurAvg());
    			returnVal = Double.parseDouble(pn.getCurAvg());
    			
    			break;
    		}
    	}
    	
    	return returnVal;
    }
    
    public double getSupplyAvg(String barcode)
    {
    	double returnVal = 0.0;
    	
    	for (int i=0; i< productStatistics.size(); i++)
    	{
    		ProductStatisticsNode pn = productStatistics.get(i);
    		
    		if (pn.getBarcode().equals(barcode))
    		{
    			System.out.println(pn.getSupplyAve());
    			returnVal = Double.parseDouble(pn.getSupplyAve());
    		}
    	}
    	
    	return returnVal;
    }
    	
    public int numNodes()
    {
    	return productStatistics.size();
    }
    
    public int numUsedNodes()
    {
    	int num = 0;
    	
    	for (int i=0; i< productStatistics.size(); i++)
    	{
    		ProductStatisticsNode pn = productStatistics.get(i);
    		
    		System.out.println(pn.getSupplyCur());
    		
    		if (Double.parseDouble(pn.getSupplyCur()
                        /*.substring(0, pn.getSupplyCur().indexOf(" count"))*/) > 0)
    		{
    			num++;
    		}
    	}
    	
    	return num;
    }
}//end of class
    
    
    


